
from Engine import *

#import SafeEval

the_engine = Engine()

# Possible input commands
#   Player commands (e.g., up, fire, jetpack)
#   Game commands (e.g., quit, display menu, pause)

#createInputCommand("player.up", self.player.giveCommand)
#createInputCommand("select_button_down")
#createInputCommand("action_button_up")

#bindInputCommandToKey("player.up", pygame.K_w)

# TODO:
"""
Add some kind of scaling parameter to Models!
Replace selection Model hack with a new selection entity
Put 'rules' code in some sort of game God object, or just the game
    It should take care of anything that involves more than one Unit
    e.g., collision events resulting in death of a Unit
            -Kill Unit
            -Increase killer Unit's score
    
Add shooting ability
    Add Bullet entity
    Soldiers can fire Bullets
    Check for collision events between Bullets and Units
    Filter Bullet collisions with own team (friendly fire)

Clean-up game code
    Perhaps create Commandable class for Unit
    
"""

class Game(object):

    def __init__(self):
        # Create game engine instance
        #self.engine = Engine()

        # TODO: Remove this crud
        #the_engine.addEventListener('render', self.render)
        #the_engine.addEventListener('tick', self.tick)
        
        # Engine settings
        the_engine.tickRate = 60
        the_engine.gravity = 0.0
        the_engine.worldMinX = -20.0
        the_engine.worldMinY = -20.0
        the_engine.worldMaxX = 20.0
        the_engine.worldMaxY = 20.0
        # TODO: Work out why turning this on makes the physics go haywire
        the_engine.usePhysicsSleeping = False
        the_engine.windowWidth = 800
        the_engine.windowHeight = 600
        the_engine.forceResolution = True
        the_engine.fullscreen = False
        #the_engine.physicsIterations = 10
        the_engine.velocity_iterations = 10
        the_engine.position_iterations = 8
        the_engine.init()

        the_engine.appendEventHandler('pygame_event', self.handlePyGameEvent)
        
        the_engine.renderer.enableParallax(True, True)

        # Set up the render layers
        the_engine.renderer.addLayer("background", 0)
        the_engine.renderer.addLayer("distant", 0.5)
        the_engine.renderer.addLayer("behind", 1)
        the_engine.renderer.addLayer("play", 1)
        the_engine.renderer.addLayer("infront", 1)
        the_engine.renderer.addLayer("foreground", 2.0)
        the_engine.renderer.addLayer("GUI", 0)

        the_engine.renderer.disableMotionBlur()

        # Set up the camera
        the_engine.renderer.camera.setMaxZoomRate(0.01)
        the_engine.renderer.camera.setZoomAccel(0.02)
        the_engine.renderer.camera.zoomTo(0.1)

        # TODO: Remove this and replace with the level's solid_geometry
        #groundShapeDef = b2PolygonDef()
        #groundShapeDef.SetAsBox(40.0, 0.1, b2Vec2(0.0, -3.0), 0.0)
        #the_engine.physics_world.GetGroundBody().CreateShape(groundShapeDef)

        world = the_engine.world

        #the_engine.renderer.camera.followBody(self.playerChar.referenceBody)

        #self.soldiers = []
        # Create two teams of Soldiers
        count = 20
        for i in range(count):
            #soldier = 
            world.createEntity(Soldier, None, 0, \
                               b2Vec2(-5.0 + 0.7*(i%int(math.sqrt(count))), \
                                      -5.0 + 0.7*(i/int(math.sqrt(count)))))
        for i in range(count):
            world.createEntity(Soldier, None, 1, \
                               b2Vec2(5.0 + 0.7*(i%int(math.sqrt(count))), \
                                      -5.0 + 0.7*(i/int(math.sqrt(count)))))

        #level = world.createEntity(Level, "level")
        #level.load("Level1")

        self.selected_units = []
        self.selected_models = []
        self.selections = []

        the_engine.run()
        
    def handlePyGameEvent(self, event):
        if event.type == pygame.MOUSEBUTTONDOWN:
            world_pos = the_engine.convertPxlsToWorldCoords(event.pos)
            if event.button == 1: # Left click
                #for unit in self.selected_units:
                #    unit.giveCommand(Command(name='move', pos=world_pos))
                self.select_start_pos = world_pos
            elif event.button == 3: # Right click
                if len(self.selected_units) > 0:
                    hit_units = the_engine.queryEntitiesAtPoint(world_pos[0], \
                                                                world_pos[1])
                    if len(hit_units) == 1:
                        hit_unit = hit_units[0]
                        if hit_unit.isAllyOfAnyOf(self.selected_units):
                            for unit in self.selected_units:
                                unit.giveCommand(Command(name='move', \
                                                         pos=hit_unit.getPos().tuple()))
                        else:
                            for unit in self.selected_units:
                                unit.giveCommand(Command(name='attack', \
                                                         target=hit_unit))
                                print "Unit", unit.name, "ordered to attack unit", hit_unit.name
                    elif len(hit_units) == 0:
                        for unit in self.selected_units:
                            unit.giveCommand(Command(name='move', \
                                                     pos=world_pos))
                    else:
                        raise Exception("What the? Mouse click hit multiple Units!")
        elif event.type == pygame.MOUSEBUTTONUP:
            world_pos = the_engine.convertPxlsToWorldCoords(event.pos)
            if event.button == 1:
                x1, y1 = self.select_start_pos
                x2, y2 = world_pos
                self.selected_units = the_engine.queryEntitiesInRect(x1, y1, \
                                                                      x2, y2)
                for selection in self.selections:
                    the_engine.world.destroyEntity(selection)
                self.selections = []
                for unit in self.selected_units:
                    s = the_engine.world.createEntity(Selection, None, unit)
                    self.selections.append(s)
                """
                for model in self.selected_models:
                    the_engine.renderer.destroyModel(model)
                self.selected_models = []
                for unit in self.selected_units:
                    # TODO: This is pretty awful!
                    self.selected_models.append( \
                        the_engine.renderer.createModel(layer="infront", \
                                mesh="soldier", \
                                texture=None, \
                                influences=[(unit.getXForm(), 1.0)], \
                                local_xform=b2XForm_identity, \
                                color=(0, 1, 0, 0.3))
                        )
                """
            
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_ESCAPE:
                self.quit()
        elif event.type == pygame.QUIT:
            self.quit()
        
        return True

    def quit(self):
        the_engine.running = False

class Selection(Entity):
    def __init__(self, engine, name, unit):
        Entity.__init__(self, engine, name)
        self.unit = unit
        self.model = self.engine.renderer.createModel(layer="infront", \
                                    mesh="selection", \
                                    texture=None, \
                                    influences=[], \
                                    local_xform=b2XForm_identity, \
                                    color=(0, 1, 0, 0.3), \
                                    scale=self.unit.size)
    def __del__(self):
        self.engine.renderer.destroyModel(self.model)
    def update(self, timeStep):
        self.model.local_xform = b2XForm(self.unit.getPos(), b2Mat22_identity)

class Command(object):
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)
"""
class Commandable(object):
    def __init__(self):
        self.has_command = {}

    def giveCommand(self, command):
        self.has_command[command.name] = True
        self.onCommandGiven(self, command)
    def retractCommand(self, command):
        self.has_command[command.name] = False
        self.onCommandRetracted(self, command)

    def onCommandGiven(self, command): pass
    def onCommandRetracted(self, command): pass
        
    def update(self):
        for commands in self.has_command
            self.applyCommand(command)

Soldier = move, attack, patrol, attack+move, guard
CommandCentre = build_worker,

class Moveable(object):
    @rule('move_given')
    def handleCommandGiven(self, pos):
        self.move_target = pos
    @rule('move_update')
    def handleCommandUpdate(self):
        between = pos - self.pos
        self.pos += between

states
    available commands

class Soldier(Unit):
    def __init__(self):
        Unit.__init__(self)
        self.addCommands('suicide', 
    def onCommandGiven(self, command):
        if command.name == 'suicice':
            pass
        else:
            Unit.onCommandGiven(self, command)
"""
class Unit(CompositeBody, Commandable):
    """
    def onCommandGiven(self, command):
        if command.name == 'suicice':
            pass
        else:
            print "Warning:", self.__class__, "given unknown command", \
                  command.name

   """ 
    def __init__(self, engine, name, team):
        CompositeBody.__init__(self, engine, name)
        self.health = 100
        self.team = team
        self.size = [0.0, 0.0]
        if self.team == 0:
            self.color = (1, 0.5, 0.5, 1)
        elif self.team == 1:
            self.color = (0.5, 0.5, 1.0, 1)
        #self.experience = 0

    def isAllyOf(self, other_unit):
        return self.team == other_unit.team
    def isAllyOfAnyOf(self, other_units):
        for other_unit in other_units:
            if self.isAllyOf(other_unit):
                return True
        return False
    def ifAllyOfAllOf(self, other_units):
        for other_unit in other_units:
            if not self.isAllyOf(other_unit):
                return False
        return True

    def applyDamage(self, damage):
        self.health -= damage
        if self.health <= 0:
            self.die()

    def die(self):
        print "Unit", self.name, "has died."

class Soldier(Unit):
    def __init__(self, engine, name, team, pos):
        Unit.__init__(self, engine, name, team)
        self.size = [1.2, 1.2]
        self.addBody('body', self.engine.bodyLoader. \
                     createCircleBody(pos, radius=self.size[0]/2, density=1), \
                     make_reference_body = True)
        #self.ref_body = self.bodies['body'] = self.engine.bodyLoader. \
        #                      createCircleBody(pos, radius=0.5, density=1)
        self.models['body'] = self.engine.renderer. \
                     createModel(layer="play", \
                                 mesh="box", \
                                 texture="soldier_standing", \
                                 influences=[(self.ref_body.GetXForm(), 1.0)], \
                                 local_xform=b2XForm_identity, \
                                 color=self.color, \
                                 scale=self.size)
        
        self.command = None
        
        #self.max_speed = 0.1
        self.move_strength = 3.0
        self.move_target = self.ref_body.GetPosition()

        self.attack_range = 8.0
        self.attack_damage = 1.0

    def giveCommand(self, command):
        self.command = command.name
        if command.name == 'move':
            self.move_target = b2Vec2(command.pos[0], command.pos[1])
        elif command.name == 'attack':
            self.attack_target = command.target
    
    def update(self, timeStep):
        # Turn the body to face the direction of motion
        if self.getLinearVelocity().Length() > 0.0:
            vel = self.getLinearVelocity()
            angle = -math.atan2(vel.x, vel.y)
            self.setXForm(b2XForm(self.getPos(), b2Mat22(angle)))
            #self.applyTorque(angle - self.getAngle())
            #self.applyDampingTorque(0.1)

        self.applyDampingForce(1.0)
        
        # Obey commands
        if self.command == 'move':
            heading = self.move_target - self.getPos()
            heading.Normalize()
            self.applyCenteredForce(self.move_strength * heading)
        elif self.command == 'attack':
            between = self.attack_target.getPos() - self.getPos()
            dist2 = between.LengthSquared()
            # If out of range, move in closer
            if dist2 > self.attack_range * self.attack_range:
                heading = between / math.sqrt(dist2)
                self.applyCenteredForce(self.move_strength * heading)
            else:
                # Turn to face the enemy
                angle = -math.atan2(between.x, between.y)
                self.setXForm(b2XForm(self.getPos(), b2Mat22(angle)))
                #print "Unit", self.name, "fires at", self.attack_target.name
                #self.attack_target.applyDamage(self.attack_damage)
        

#setTrace()

game = Game()
#import cProfile
#output = cProfile.run('Game()', 'profile.dat')
#print output

#import pstats
#p = pstats.Stats('profile.dat')
#p.sort_stats('time').print_stats(20)

#import pylab
#pylab.plot(func, totaltime)
#pylab.show()
